/* caStore.c */

/*
 * Licensed to the ConArtist Development Team (CADT).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The CADT licenses this file to you under the Apache License, 
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string.h>
#include <sys/stat.h>

#include <apr.h>
#include <apr_dso.h>
#include <apr_thread_rwlock.h>
#include <apr_strings.h>
#include <apr_file_io.h>

#include <libxml/tree.h>

#include "conartist.h"
#include "caStore.h"
#include "caLog.h"

#include "caPrivate.h"

static apr_thread_rwlock_t *modLock = NULL;

/* Each store that is created is referenced from their names via this
 * hash to allow quick lookups. */
static apr_hash_t *storeHash = NULL;
static apr_hash_t *typeHash = NULL;

static apr_pool_t *storePool = NULL;

/* Each store module loaded will be represented by one of these 
 * structures. They are all linked together to give us a record of
 * the store modules we have available.
 */
struct storeLibrary {
    struct storeLibrary *next;
    apr_dso_handle_t *dso;
    struct caStoreModule *ptr;
} *storeModules = NULL;

/* Every store created is attached to a linked list as well as the name
 * hash to allow us to easily walk through all of the created stores.
 */
struct createdStore {
    char *name;
    struct caStore *store;
};

struct storeNotify {
    struct storeNotify *next;
    struct storeNotify *last;
    char *path;
    int which;
    storeNotifyCb *func;
    void *data;
};

apr_status_t storeInit(void)
{
    apr_status_t rv = apr_pool_create(&storePool, globalPool);
    if (rv != APR_SUCCESS)
        return rv;
    storeHash = apr_hash_make(storePool);
    if (!storeHash)
        return APR_ENOMEM;
    typeHash = apr_hash_make(storePool);
    if (!typeHash)
        return APR_ENOMEM;
    rv = apr_thread_rwlock_create(&modLock, storePool);
    if (rv != APR_SUCCESS) {
        apr_pool_destroy(storePool);
        return rv;
    }
    return rv;
}

/* This adds a created, named store */
static apr_status_t insertStore(const char *name, struct caStore *store)
{
    struct createdStore *cs = NULL;
    apr_status_t rv;

    if (!name || !store)
        return APR_EINVAL;

    cs = apr_pcalloc(storePool, sizeof(*cs));
    if (!cs)
        return APR_ENOMEM;
    cs->store = store;
    cs->name = apr_pstrdup(storePool, name);

    rv = apr_thread_rwlock_wrlock(modLock);
    if (rv != APR_SUCCESS)
        return rv;
    apr_hash_set(storeHash, cs->name, APR_HASH_KEY_STRING, cs);
    apr_thread_rwlock_unlock(modLock);

    caLog(CA_LOG_INFO, "Inserted store '%s'", name);
    return rv;
}

apr_status_t caStoreFind(struct caStore **ss, const char *name)
{
    struct createdStore *cs = NULL;
    apr_status_t rv;

    *ss = NULL;

    if (!name) {
        caLog(CA_LOG_ERROR, "No store name specified in call to caStoreFind");
        return APR_EINVAL;
    }

    rv = apr_thread_rwlock_rdlock(modLock);
    if (rv != APR_SUCCESS)
        return rv;

    cs = apr_hash_get(storeHash, name, APR_HASH_KEY_STRING);
    apr_thread_rwlock_unlock(modLock);

    if (cs)
        *ss = cs->store;

    if (*ss)
        return APR_SUCCESS;
    return APR_EINVAL;
}

struct caStoreModule *storeFindType(const char *name)
{
    struct caStoreModule *sm = NULL;
    apr_status_t rv;

    if (!name)
        return NULL;
    rv = apr_thread_rwlock_rdlock(modLock);
    if (rv != APR_SUCCESS)
        return NULL;

    sm = apr_hash_get(typeHash, name, APR_HASH_KEY_STRING);
    apr_thread_rwlock_unlock(modLock);

    return sm;
}

apr_status_t addStoreModule(apr_dso_handle_t *dso, struct caModule *mod)
{
    apr_status_t rv;
    struct caStoreModule *ck;
    struct caStoreModule *sMod = (struct caStoreModule *)mod;

    if (mod->type != CAM_STORE)
        return CAM_ERR_TYPE;

    ck = storeFindType(sMod->type);
    if (ck) {
        caLog(CA_LOG_ERROR, "Trying to add duplicate type of store module, '%s'",
              sMod->type);
        return APR_EINVAL;
    }

    rv = apr_thread_rwlock_wrlock(modLock);
    if (rv == APR_SUCCESS) {
        apr_hash_set(typeHash, sMod->type, APR_HASH_KEY_STRING, sMod);
        rv = apr_thread_rwlock_unlock(modLock);

        caLog(CA_LOG_INFO, "Added store module '%s' which implements store "
              "type '%s'", mod->name, sMod->type);
    }

    return rv;
}

apr_status_t caStoreOpen(const char *type, const char *name,
                         xmlNode *node)
{
    struct caStore *store = NULL;
    apr_status_t rv;
    xmlNode *n;
    const char *fspath = NULL;
    struct caStoreModule *sm = NULL;

    if (! type || !name || !node)
        return APR_EINVAL;

    sm = storeFindType(type);
    if (!sm) {
        caLog(CA_LOG_ERROR, "Unable to find a store of type '%s'", type);
        return APR_EINVAL;
    }

    store = apr_pcalloc(storePool, sizeof(*store));
    if (!store)
        return APR_ENOMEM;

    if (apr_pool_create(&store->st_pool, storePool) != APR_SUCCESS)
        return APR_ENOMEM;

    store->notifyHash = apr_hash_make(store->st_pool);
    if (!store->notifyHash)
        goto clean_exit;

    rv = apr_thread_rwlock_create(&store->lock, store->st_pool);
    if (rv != APR_SUCCESS)
        goto clean_exit;

    store->name = apr_pstrdup(store->st_pool, name);
    store->config = apr_table_make(store->st_pool, 1);

    /* Read data from configuration node */
    for (n = node->children; n; n = n->next) {
        char *val;

        if (xmlStrcmp(n->name, (xmlChar *)"comment") == 0 ||
            xmlStrcmp(n->name, (xmlChar *)"text") == 0)
            continue;

        val = (char *)xmlNodeGetContent(n);
        if (val) {
            apr_table_set(store->config, (char *)n->name, val);
            xmlFree(val);
        }
    }

    /* If we've been given a local file path we need to ensure that
     * it's available, and if not create it. */
    fspath = apr_table_get(store->config, "LocalPath");
    rv = checkDirectory(apr_table_get(store->config, "LocalPath"), 1);
    if (rv != APR_SUCCESS) {
        caLog(CA_LOG_ERROR, "Invalid LocalPath (%s) specified.",
              apr_table_get(store->config, "LocalPath"));
        return APR_EINVAL;
    }   

    rv = sm->openStore(store);
    if (rv != APR_SUCCESS) {
        caLog(CA_LOG_WARN, "Store type '%s' was unable to open '%s'",
              type, name);
        goto clean_exit;
    }

    store->module = sm;
    if (insertStore(name, store) == APR_SUCCESS)
        return APR_SUCCESS;

clean_exit:
    caLog(CA_LOG_WARN, "Unable to insert store into lists");
    apr_pool_destroy(store->st_pool);
    return APR_EINVAL;
}

int caStoreClose(struct caStore *store)
{
    /* Remove the store from the store hash */
    apr_status_t rv = apr_thread_rwlock_wrlock(modLock);
    if (rv != APR_SUCCESS)
        return rv;
    apr_hash_set(storeHash, store->name, APR_HASH_KEY_STRING, NULL);
    if (store->module->closeStore)
        store->module->closeStore(store);
    apr_thread_rwlock_unlock(modLock);
    caLog(CA_LOG_INFO, "Removing store '%s'\n", store->name);
    apr_pool_destroy(store->st_pool);

    return APR_SUCCESS;
}

struct caDir *caOpenDirectory(struct caStore *s, const char *path,
                              apr_pool_t *pool)
{
    struct caDir *d = NULL;
    apr_status_t rv;
caLog(CA_LOG_DEBUG, "caOpenDirectory(%s)", path);

    if (!path || !s->privateData || !s->module)
        return d;

    if (s->module->openDir)
        rv = s->module->openDir(&d, path, pool, s->privateData);
    else {
        char *fullPath;
        rv = s->module->getPath(&fullPath, path, pool, s->privateData);
        if (rv == APR_SUCCESS) {
            apr_dir_t *pd;
            rv = apr_dir_open(&pd, fullPath, pool);
            if (rv == APR_SUCCESS) {
                d = apr_pcalloc(pool, sizeof(*d));
                d->pool = pool;
                d->path = apr_pstrdup(pool, path);
                d->privateData = pd;
                d->store = s;
                return d;
            }
        }
    }

    return d;
}

apr_status_t caReadDirectory(struct caDirEntry *ent, struct caDir *dir)
{
    apr_status_t rv;
    apr_dir_t *d = (apr_dir_t *)dir->privateData;
    apr_int32_t wanted = APR_FINFO_DIRENT;

    if (dir->store->module->readDir)
        return dir->store->module->readDir(ent, dir, dir->privateData);

again:
    rv = apr_dir_read(&ent->finfo, wanted, d);
    if (rv == APR_SUCCESS) {
        if (ent->finfo.name[0] == '.')
            goto again;
    }
    /* Make a complete path... */
    apr_filepath_merge((char **)&ent->finfo.fname, dir->path, 
                       ent->finfo.name, 0, dir->pool);
    return rv;
}    

apr_status_t caDirEntryFilename(char **fspath, struct caDirEntry *ent, 
                                struct caDir *dir)
{
    return dir->store->module->getPath(fspath, ent->finfo.fname, dir->pool,
                                       dir->store->privateData);
}

apr_status_t caCloseDirectory(struct caDir *dir)
{
    apr_dir_t *d = (apr_dir_t *)dir->privateData;

    if (dir->store->module->closeDir)
        return dir->store->module->closeDir(dir, dir->privateData);

    return apr_dir_close(d);
}    

apr_status_t caOpen(struct caFile **fh, struct caStore *s, 
                    const char *path, apr_pool_t *pool)
{
    struct caFile *ff = NULL;
    apr_file_t *f = NULL;
    char *fullPath;
    apr_status_t rv;

    if (!path || !s->privateData || !s->module)
        return APR_EINVAL;

    if (s->module->openFile)
        return s->module->openFile(fh, path, pool, s->privateData);

    rv = s->module->getPath(&fullPath, path, pool, s->privateData);
    if (rv == APR_SUCCESS) {
        rv = apr_file_open(&f, fullPath, APR_READ | APR_WRITE, 0, pool);
        if (rv == APR_SUCCESS) {
            ff = apr_pcalloc(pool, sizeof(*ff));
            if (!ff)
                return APR_ENOMEM;
            ff->pool = pool;
            ff->path = apr_pstrdup(pool, path);
            ff->store = s->module;
            ff->privateData = f;
            *fh = ff;
        }
    }
    return rv;
}

apr_status_t caClose(struct caFile *fh)
{
    apr_file_t *f = (apr_file_t *)fh;
    if (fh->store->closeFile)
        return fh->store->closeFile(fh->privateData);
    return apr_file_close(f);    
}

apr_status_t caRead(struct caFile *fh, char *buffer, apr_size_t *blen)
{
    apr_file_t *f = NULL;

    if (fh->store->readFile)
        return fh->store->readFile(fh, buffer, blen);

    f = (apr_file_t *)fh->privateData;
    return apr_file_read(f, buffer, blen);
}

apr_status_t caWrite(struct caFile *fh, const char *buffer, 
                     apr_size_t *blen)
{
    apr_file_t *f = NULL;

    if (fh->store->writeFile)
        return fh->store->writeFile(fh, buffer, blen);

    f = (apr_file_t *)fh->privateData;
    return apr_file_write(f, buffer, blen);
}

apr_status_t caFileGet(char **buffer, apr_size_t *bytes, 
                       const char *path, struct caStore *store,
                       apr_pool_t *p)
{
    apr_file_t *fh = NULL;
    apr_finfo_t fi;
    apr_size_t sz;
    apr_status_t rv;
    char *fspath = NULL, *buff = NULL;
    int pos = 0;

    while (path[pos] != ':' && path[pos] != '\0')
        pos++;
    if (path[pos] == '\0')
        pos = 0;
    else
        pos++;

    if (store) {
        rv = (store->module->getPath)(&fspath, &path[pos], p, store->privateData);
        if (rv != APR_SUCCESS)
            return rv;
    } else
        fspath = (char *)path;

    rv = apr_stat(&fi, fspath, APR_FINFO_SIZE, p);
    if (rv != APR_SUCCESS)
        return rv;

    buff = apr_palloc(p, fi.size);
    if (!buff)
        return APR_ENOMEM;

    rv = apr_file_open(&fh, fspath, APR_READ, 0, p);
    if (rv != APR_SUCCESS)
        return rv;
    rv = apr_file_read_full(fh, buff, fi.size, &sz);
    apr_file_close(fh);
    *buffer = buff;
    *bytes = sz;
    return rv;
}

apr_status_t storeNotifyAdd(struct caStore *st, const char *fn,
                            int which, storeNotifyCb *func, void *data)
{
    apr_status_t rv = APR_EINVAL;
    struct storeNotify *sn = NULL;

    if (!st || !fn)
        return rv;

    rv = apr_thread_rwlock_rdlock(st->lock);
    if (rv != APR_SUCCESS)
        return rv;
    sn = apr_hash_get(st->notifyHash, fn, APR_HASH_KEY_STRING);
    apr_thread_rwlock_unlock(st->lock);


    rv = apr_thread_rwlock_wrlock(st->lock);
    if (rv != APR_SUCCESS)
        return rv;

    if (sn) {
        struct storeNotify *snn = sn;
        for (; snn; snn = snn->next) {
            if (func == snn->func && snn->data == data) {
                snn->which = which;
                rv = APR_SUCCESS;
                break;
            }
        }
    }
    if (rv != APR_SUCCESS) {
        struct storeNotify *snn = apr_pcalloc(st->st_pool, sizeof(*snn));
        if (snn) {
            snn->func = func;
            snn->data = data;
            snn->path = apr_pstrdup(st->st_pool, fn);
            snn->which = which;
            if (sn) {
                sn->last->next = snn;
                sn->last = snn;
            } else {
                snn->last = snn;
                apr_hash_set(st->notifyHash, snn->path, APR_HASH_KEY_STRING, snn);
            }
            rv = APR_SUCCESS;
        } else
            rv = APR_ENOMEM;
    }
    apr_thread_rwlock_unlock(st->lock);


    return APR_SUCCESS;
}
